home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 1999 November / SGI Freeware 1999 November - Disc 1.iso / dist / fw_cvs.idb / usr / freeware / lib / cvs / contrib / rcs2log.z / rcs2log
Text File  |  1999-04-16  |  15KB  |  606 lines

  1. #! /bin/sh
  2.  
  3. # RCS to ChangeLog generator
  4.  
  5. # Generate a change log prefix from RCS files and the ChangeLog (if any).
  6. # Output the new prefix to standard output.
  7. # You can edit this prefix by hand, and then prepend it to ChangeLog.
  8.  
  9. # Ignore log entries that start with `#'.
  10. # Clump together log entries that start with `{topic} ',
  11. # where `topic' contains neither white space nor `}'.
  12.  
  13. # Author: Paul Eggert <eggert@twinsun.com>
  14.  
  15. # Copyright 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
  16.  
  17. # This program is free software; you can redistribute it and/or modify
  18. # it under the terms of the GNU General Public License as published by
  19. # the Free Software Foundation; either version 2, or (at your option)
  20. # any later version.
  21. # This program is distributed in the hope that it will be useful,
  22. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  24. # GNU General Public License for more details.
  25. # You should have received a copy of the GNU General Public License
  26. # along with this program; see the file COPYING.  If not, write to the
  27. # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  28. # Boston, MA 02111-1307, USA.
  29.  
  30. tab='    '
  31. nl='
  32. '
  33.  
  34. # Parse options.
  35.  
  36. # defaults
  37. : ${AWK=awk}
  38. : ${TMPDIR=/tmp}
  39. hostname= # name of local host (if empty, will deduce it later)
  40. indent=8 # indent of log line
  41. length=79 # suggested max width of log line
  42. logins= # login names for people we know fullnames and mailaddrs of
  43. loginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets
  44. recursive= # t if we want recursive rlog
  45. rlog_options= # options to pass to rlog
  46. tabwidth=8 # width of horizontal tab
  47.  
  48. while :
  49. do
  50.     case $1 in
  51.     -i)    indent=${2?}; shift;;
  52.     -h)    hostname=${2?}; shift;;
  53.     -l)    length=${2?}; shift;;
  54.     -[nu])    # -n is obsolescent; it is replaced by -u.
  55.         case $1 in
  56.         -n)    case ${2?}${3?}${4?} in
  57.             *"$tab"* | *"$nl"*)
  58.                 echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed"
  59.                 exit 1
  60.             esac
  61.             loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4
  62.             shift; shift; shift;;
  63.         -u)
  64.             # If $2 is not tab-separated, use colon for separator.
  65.             case ${2?} in
  66.             *"$nl"*)
  67.                 echo >&2 "$0: -u '$2': newlines not allowed"
  68.                 exit 1;;
  69.             *"$tab"*)
  70.                 t=$tab;;
  71.             *)
  72.                 t=:
  73.             esac
  74.             case $2 in
  75.             *"$t"*"$t"*"$t"*)
  76.                 echo >&2 "$0: -u '$2': too many fields"
  77.                 exit 1;;
  78.             *"$t"*"$t"*)
  79.                 ;;
  80.             *)
  81.                 echo >&2 "$0: -u '$2': not enough fields"
  82.                 exit 1
  83.             esac
  84.             loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2
  85.             shift
  86.         esac
  87.         logins=$logins$nl$login
  88.         ;;
  89.     -r)    rlog_options=$rlog_options$nl${2?}; shift;;
  90.     -R)    recursive=t;;
  91.     -t)    tabwidth=${2?}; shift;;
  92.     -*)    echo >&2 "$0: usage: $0 [options] [file ...]
  93. Options:
  94.     [-h hostname] [-i indent] [-l length] [-R] [-r rlog_option]
  95.     [-t tabwidth] [-u 'login<TAB>fullname<TAB>mailaddr']..."
  96.         exit 1;;
  97.     *)    break
  98.     esac
  99.     shift
  100. done
  101.  
  102. month_data='
  103.     m[0]="Jan"; m[1]="Feb"; m[2]="Mar"
  104.     m[3]="Apr"; m[4]="May"; m[5]="Jun"
  105.     m[6]="Jul"; m[7]="Aug"; m[8]="Sep"
  106.     m[9]="Oct"; m[10]="Nov"; m[11]="Dec"
  107.  
  108.     # days in non-leap year thus far, indexed by month (0-12)
  109.     mo[0]=0; mo[1]=31; mo[2]=59; mo[3]=90
  110.     mo[4]=120; mo[5]=151; mo[6]=181; mo[7]=212
  111.     mo[8]=243; mo[9]=273; mo[10]=304; mo[11]=334
  112.     mo[12]=365
  113. '
  114.  
  115.  
  116. # Put rlog output into $rlogout.
  117.  
  118. # If no rlog options are given,
  119. # log the revisions checked in since the first ChangeLog entry.
  120. case $rlog_options in
  121. '')
  122.     date=1970-01-01
  123.     if test -s ChangeLog
  124.     then
  125.         # Add 1 to seconds to avoid duplicating most recent log.
  126.         e='
  127.             /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{
  128.                 '"$month_data"'
  129.                 year = $5
  130.                 for (i=0; i<=11; i++) if (m[i] == $2) break
  131.                 dd = $3
  132.                 hh = substr($0,12,2)
  133.                 mm = substr($0,15,2)
  134.                 ss = substr($0,18,2)
  135.                 ss++
  136.                 if (ss == 60) {
  137.                     ss = 0
  138.                     mm++
  139.                     if (mm == 60) {
  140.                         mm = 0
  141.                         hh++
  142.                         if (hh == 24) {
  143.                             hh = 0
  144.                             dd++
  145.                             monthdays = mo[i+1] - mo[i]
  146.                             if (i == 1 && year%4 == 0 && (year%100 != 0 || year%400 == 0)) monthdays++
  147.                             if (dd == monthdays + 1) {
  148.                                 dd = 1
  149.                                 i++
  150.                                 if (i == 12) {
  151.                                     i = 0
  152.                                     year++
  153.                                 }
  154.                             }
  155.                         }
  156.                     }
  157.                 }
  158.                 printf "%02d/%02d/%d %02d:%02d:%02d\n", i+1,dd,year,hh,mm,ss
  159.                 exit
  160.             }
  161.         '
  162.         d=`$AWK "$e" <ChangeLog` || exit
  163.         case $d in
  164.         ?*) date=$d
  165.         esac
  166.     fi
  167.     datearg="-d>$date"
  168. esac
  169.  
  170. # If CVS is in use, examine its repository, not the normal RCS files.
  171. if test ! -f CVS/Repository
  172. then
  173.     rlog=rlog
  174.     repository=
  175. else
  176.     rlog='cvs log'
  177.     repository=`sed 1q <CVS/Repository` || exit
  178.     test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit
  179.     case $CVSROOT in
  180.     *:/*)
  181.         # remote repository
  182.         ;;
  183.     *)
  184.         # local repository
  185.         case $repository in
  186.         /*) ;;
  187.         *) repository=${CVSROOT?}/$repository
  188.         esac
  189.         if test ! -d "$repository"
  190.         then
  191.             echo >&2 "$0: $repository: bad repository (see CVS/Repository)"
  192.             exit 1
  193.         fi
  194.     esac
  195. fi
  196.  
  197. # With no arguments, examine all files under the RCS directory.
  198. case $# in
  199. 0)
  200.     case $repository in
  201.     '')
  202.         oldIFS=$IFS
  203.         IFS=$nl
  204.         case $recursive in
  205.         t)
  206.             RCSdirs=`find . -name RCS -type d -print`
  207.             filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||'
  208.             files=`
  209.                 {
  210.                     case $RCSdirs in
  211.                     ?*) find $RCSdirs -type f -print
  212.                     esac
  213.                     find . -name '*,v' -print
  214.                 } |
  215.                 sort -u |
  216.                 sed "$filesFromRCSfiles"
  217.             `;;
  218.         *)
  219.             files=
  220.             for file in RCS/.* RCS/* .*,v *,v
  221.             do
  222.                 case $file in
  223.                 RCS/. | RCS/..) continue;;
  224.                 RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue
  225.                 esac
  226.                 files=$files$nl$file
  227.             done
  228.             case $files in
  229.             '') exit 0
  230.             esac
  231.         esac
  232.         set x $files
  233.         shift
  234.         IFS=$oldIFS
  235.     esac
  236. esac
  237.  
  238. llogout=$TMPDIR/rcs2log$$l
  239. rlogout=$TMPDIR/rcs2log$$r
  240. trap exit 1 2 13 15
  241. trap "rm -f $llogout $rlogout; exit 1" 0
  242.  
  243. case $rlog_options in
  244. ?*) $rlog $rlog_options ${1+"$@"} >$rlogout;;
  245. '') $rlog "$datearg" ${1+"$@"} >$rlogout
  246. esac || exit
  247.  
  248.  
  249. # Get the full name of each author the logs mention, and set initialize_fullname
  250. # to awk code that initializes the `fullname' awk associative array.
  251. # Warning: foreign authors (i.e. not known in the passwd file) are mishandled;
  252. # you have to fix the resulting output by hand.
  253.  
  254. initialize_fullname=
  255. initialize_mailaddr=
  256.  
  257. case $loginFullnameMailaddrs in
  258. ?*)
  259.     case $loginFullnameMailaddrs in
  260.     *\"* | *\\*)
  261.         sed 's/["\\]/\\&/g' >$llogout <<EOF || exit
  262. $loginFullnameMailaddrs
  263. EOF
  264.         loginFullnameMailaddrs=`cat $llogout`
  265.     esac
  266.  
  267.     oldIFS=$IFS
  268.     IFS=$nl
  269.     for loginFullnameMailaddr in $loginFullnameMailaddrs
  270.     do
  271.         case $loginFullnameMailaddr in
  272.         *"$tab"*) IFS=$tab;;
  273.         *) IFS=:
  274.         esac
  275.         set x $loginFullnameMailaddr
  276.         login=$2
  277.         fullname=$3
  278.         mailaddr=$4
  279.         initialize_fullname="$initialize_fullname
  280.             fullname[\"$login\"] = \"$fullname\""
  281.         initialize_mailaddr="$initialize_mailaddr
  282.             mailaddr[\"$login\"] = \"$mailaddr\""
  283.     done
  284.     IFS=$oldIFS
  285. esac
  286.  
  287. case $llogout in
  288. ?*) sort -u -o $llogout <<EOF || exit
  289. $logins
  290. EOF
  291. esac
  292. output_authors='/^date: / {
  293.     if ($2 ~ /^[0-9]*[-\/][0-9][0-9][-\/][0-9][0-9]$/ && $3 ~ /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9][-+0-9:]*;$/ && $4 == "author:" && $5 ~ /^[^;]*;$/) {
  294.         print substr($5, 1, length($5)-1)
  295.     }
  296. }'
  297. authors=`
  298.     $AWK "$output_authors" <$rlogout |
  299.     case $llogout in
  300.     '') sort -u;;
  301.     ?*) sort -u | comm -23 - $llogout
  302.     esac
  303. `
  304. case $authors in
  305. ?*)
  306.     cat >$llogout <<EOF || exit
  307. $authors
  308. EOF
  309.     initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/'
  310.     initialize_author=`sed -e "$initialize_author_script" <$llogout`
  311.     awkscript='
  312.         BEGIN {
  313.             alphabet = "abcdefghijklmnopqrstuvwxyz"
  314.             ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  315.             '"$initialize_author"'
  316.         }
  317.         {
  318.             if (author[$1]) {
  319.                 fullname = $5
  320.                 if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) {
  321.                     # Remove the junk from fullnames like "0000-Admin(0000)".
  322.                     fullname = substr(fullname, index(fullname, "-") + 1)
  323.                     fullname = substr(fullname, 1, index(fullname, "(") - 1)
  324.                 }
  325.                 if (fullname ~ /,[^ ]/) {
  326.                     # Some sites put comma-separated junk after the fullname.
  327.                     # Remove it, but leave "Bill Gates, Jr" alone.
  328.                     fullname = substr(fullname, 1, index(fullname, ",") - 1)
  329.                 }
  330.                 abbr = index(fullname, "&")
  331.                 if (abbr) {
  332.                     a = substr($1, 1, 1)
  333.                     A = a
  334.                     i = index(alphabet, a)
  335.                     if (i) A = substr(ALPHABET, i, 1)
  336.                     fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1)
  337.                 }
  338.  
  339.                 # Quote quotes and backslashes properly in full names.
  340.                 # Do not use gsub; traditional awk lacks it.
  341.                 quoted = ""
  342.                 rest = fullname
  343.                 for (;;) {
  344.                     p = index(rest, "\\")
  345.                     q = index(rest, "\"")
  346.                     if (p) {
  347.                         if (q && q<p) p = q
  348.                     } else {
  349.                         if (!q) break
  350.                         p = q
  351.                     }
  352.                     quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1)
  353.                     rest = substr(rest, p+1)
  354.                 }
  355.  
  356.                 printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest
  357.                 author[$1] = 0
  358.             }
  359.         }
  360.     '
  361.  
  362.     initialize_fullname=`
  363.         (
  364.             cat /etc/passwd
  365.             for author in $authors
  366.             do nismatch $author passwd.org_dir
  367.             done
  368.             ypmatch $authors passwd
  369.         ) 2>/dev/null |
  370.         $AWK -F: "$awkscript"
  371.     `$initialize_fullname
  372. esac
  373.  
  374.  
  375. # Function to print a single log line.
  376. # We don't use awk functions, to stay compatible with old awk versions.
  377. # `Log' is the log message (with \n replaced by \r).
  378. # `files' contains the affected files.
  379. printlogline='{
  380.  
  381.     # Following the GNU coding standards, rewrite
  382.     #    * file: (function): comment
  383.     # to
  384.     #    * file (function): comment
  385.     if (Log ~ /^\([^)]*\): /) {
  386.         i = index(Log, ")")
  387.         files = files " " substr(Log, 1, i)
  388.         Log = substr(Log, i+3)
  389.     }
  390.  
  391.     # If "label: comment" is too long, break the line after the ":".
  392.     sep = " "
  393.     if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, CR)) sep = "\n" indent_string
  394.  
  395.     # Print the label.
  396.     printf "%s*%s:", indent_string, files
  397.  
  398.     # Print each line of the log, transliterating \r to \n.
  399.     while ((i = index(Log, CR)) != 0) {
  400.         logline = substr(Log, 1, i-1)
  401.         if (logline ~ /[^'"$tab"' ]/) {
  402.             printf "%s%s\n", sep, logline
  403.         } else {
  404.             print ""
  405.         }
  406.         sep = indent_string
  407.         Log = substr(Log, i+1)
  408.     }
  409. }'
  410.  
  411. case $hostname in
  412. '')
  413.     hostname=`(
  414.         hostname || uname -n || uuname -l || cat /etc/whoami
  415.     ) 2>/dev/null` || {
  416.         echo >&2 "$0: cannot deduce hostname"
  417.         exit 1
  418.     }
  419.  
  420.     case $hostname in
  421.     *.*) ;;
  422.     *)
  423.         domainname=`(domainname) 2>/dev/null` &&
  424.         case $domainname in
  425.         *.*) hostname=$hostname.$domainname
  426.         esac
  427.     esac
  428. esac
  429.  
  430.  
  431. # Process the rlog output, generating ChangeLog style entries.
  432.  
  433. # First, reformat the rlog output so that each line contains one log entry.
  434. # Transliterate \n to \r so that multiline entries fit on a single line.
  435. # Discard irrelevant rlog output.
  436. $AWK <$rlogout '
  437.     BEGIN { repository = "'"$repository"'" }
  438.     /^RCS file:/ {
  439.         if (repository != "") {
  440.             filename = $3
  441.             if (substr(filename, 1, length(repository) + 1) == repository "/") {
  442.                 filename = substr(filename, length(repository) + 2)
  443.             }
  444.             if (filename ~ /,v$/) {
  445.                 filename = substr(filename, 1, length(filename) - 2)
  446.             }
  447.         }
  448.     }
  449.     /^Working file:/ { if (repository == "") filename = $3 }
  450.     /^date: /, /^(-----------*|===========*)$/ {
  451.         if ($0 ~ /^branches: /) { next }
  452.         if ($0 ~ /^date: [0-9][- +\/0-9:]*;/) {
  453.             date = $2
  454.             if (date ~ /-/) {
  455.                 # An ISO format date.  Replace all "-"s with "/"s.
  456.                 newdate = ""
  457.                 while ((i = index(date, "-")) != 0) {
  458.                     newdate = newdate substr(date, 1, i-1) "/"
  459.                     date = substr(date, i+1)
  460.                 }
  461.                 date = newdate date
  462.             }
  463.             # Ignore any time zone; ChangeLog has no room for it.
  464.             time = substr($3, 1, 8)
  465.             author = substr($5, 1, length($5)-1)
  466.             printf "%s %s %s %s %c", filename, date, time, author, 13
  467.             next
  468.         }
  469.         if ($0 ~ /^(-----------*|===========*)$/) { print ""; next }
  470.         printf "%s%c", $0, 13
  471.     }
  472. ' |
  473.  
  474. # Now each line is of the form
  475. # FILENAME YYYY/MM/DD HH:MM:SS AUTHOR \rLOG
  476. #    where \r stands for a carriage return,
  477. #    and each line of the log is terminated by \r instead of \n.
  478. # Sort the log entries, first by date+time (in reverse order),
  479. # then by author, then by log entry, and finally by file name (just in case).
  480. sort +1 -3r +3 +0 |
  481.  
  482. # Finally, reformat the sorted log entries.
  483. $AWK '
  484.     BEGIN {
  485.         # Some awk variants do not understand "\r" or "\013", so we have to
  486.         # put a carriage return directly in the file.
  487.         CR="" # <-- There is a single CR between the " chars here.
  488.  
  489.         # Initialize the fullname and mailaddr associative arrays.
  490.         '"$initialize_fullname"'
  491.         '"$initialize_mailaddr"'
  492.  
  493.         # Initialize indent string.
  494.         indent_string = ""
  495.         i = '"$indent"'
  496.         if (0 < '"$tabwidth"')
  497.             for (;  '"$tabwidth"' <= i;  i -= '"$tabwidth"')
  498.                 indent_string = indent_string "\t"
  499.         while (1 <= i--)
  500.             indent_string = indent_string " "
  501.  
  502.         # Set up date conversion tables.
  503.         # RCS uses a nice, clean, sortable format,
  504.         # but ChangeLog wants the traditional, ugly ctime format.
  505.  
  506.         # January 1, 0 AD (Gregorian) was Saturday = 6
  507.         EPOCH_WEEKDAY = 6
  508.         # Of course, there was no 0 AD, but the algorithm works anyway.
  509.  
  510.         w[0]="Sun"; w[1]="Mon"; w[2]="Tue"; w[3]="Wed"
  511.         w[4]="Thu"; w[5]="Fri"; w[6]="Sat"
  512.  
  513.         '"$month_data"'
  514.     }
  515.  
  516.     {
  517.         newlog = substr($0, 1 + index($0, CR))
  518.  
  519.         # Ignore log entries prefixed by "#".
  520.         if (newlog ~ /^#/) { next }
  521.  
  522.         if (Log != newlog || date != $2 || author != $4) {
  523.  
  524.             # The previous log and this log differ.
  525.  
  526.             # Print the old log.
  527.             if (date != "") '"$printlogline"'
  528.  
  529.             # Logs that begin with "{clumpname} " should be grouped together,
  530.             # and the clumpname should be removed.
  531.             # Extract the new clumpname from the log header,
  532.             # and use it to decide whether to output a blank line.
  533.             newclumpname = ""
  534.             sep = "\n"
  535.             if (date == "") sep = ""
  536.             if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) {
  537.                 i = index(newlog, "}")
  538.                 newclumpname = substr(newlog, 1, i)
  539.                 while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++
  540.                 newlog = substr(newlog, i+1)
  541.                 if (clumpname == newclumpname) sep = ""
  542.             }
  543.             printf sep
  544.             clumpname = newclumpname
  545.  
  546.             # Get ready for the next log.
  547.             Log = newlog
  548.             if (files != "")
  549.                 for (i in filesknown)
  550.                     filesknown[i] = 0
  551.             files = ""
  552.         }
  553.         if (date != $2  ||  author != $4) {
  554.             # The previous date+author and this date+author differ.
  555.             # Print the new one.
  556.             date = $2
  557.             author = $4
  558.  
  559.             # Convert nice RCS date like "1992/01/03 00:03:44"
  560.             # into ugly ctime date like "Fri Jan  3 00:03:44 1992".
  561.             # Calculate day of week from Gregorian calendar.
  562.             i = index($2, "/")
  563.             year = substr($2, 1, i-1) + 0
  564.             monthday = substr($2, i+1)
  565.             i = index(monthday, "/")
  566.             month = substr(monthday, 1, i-1) + 0
  567.             day = substr(monthday, i+1) + 0
  568.             leap = 0
  569.             if (2 < month && year%4 == 0 && (year%100 != 0 || year%400 == 0)) leap = 1
  570.             days_since_Sunday_before_epoch = EPOCH_WEEKDAY + year * 365 + int((year + 3) / 4) - int((year + 99) / 100) + int((year + 399) / 400) + mo[month-1] + leap + day - 1
  571.  
  572.             # Print "date  fullname  (email address)".
  573.             # Get fullname and email address from associative arrays;
  574.             # default to author and author@hostname if not in arrays.
  575.             if (fullname[author])
  576.                 auth = fullname[author]
  577.             else
  578.                 auth = author
  579.             printf "%s %s %2d %s %d  %s  ", w[days_since_Sunday_before_epoch%7], m[month-1], day, $3, year, auth
  580.             if (mailaddr[author])
  581.                 printf "<%s>\n\n", mailaddr[author]
  582.             else
  583.                 printf "<%s@%s>\n\n", author, "'"$hostname"'"
  584.         }
  585.         if (! filesknown[$1]) {
  586.             filesknown[$1] = 1
  587.             if (files == "") files = " " $1
  588.             else files = files ", " $1
  589.         }
  590.     }
  591.     END {
  592.         # Print the last log.
  593.         if (date != "") {
  594.             '"$printlogline"'
  595.             printf "\n"
  596.         }
  597.     }
  598. ' &&
  599.  
  600.  
  601. # Exit successfully.
  602.  
  603. exec rm -f $llogout $rlogout
  604.